import { watch, inject, ref, reactive, toRefs } from "vue-demi";
import { Session } from "../session";
import {PromiseEx} from '../../utils';

const hskey = 'elec-socket',
baseAddr = import.meta.env.DEV?'wss://elec.exti.cc/center' : (location.protocol + '//' + location.host+'/center').replace('http', 'ws'),
parseName = (c)=>{
    for(let k in c){
        switch(k){
        case '_get':
        case '_next':
        case '_multi':
            break;
        default:
            return k;
        }
    }
    return null;
},
execSubCondition = (cond, data, parent)=>{
    let get = cond._get ?? false, next = cond._next ?? null, name = parseName(cond), _cond = cond[name], multi = cond._multi ?? false;
    let _r = data.filter(v=>v.name == name), have = false, final = {};
    if(_r.length > 0) {
        let r = _r[0], res = false;
        if(undefined !== r.put){
            res = multi ? r.put.map(v=>_cond('put',v, parent)?v:null).filter(v=>v!==null) : r.put.find(v=>_cond('put', v, parent));
        }

        if(undefined !== r.up){
            let up = multi ? r.up.map(v=>_cond('up',v, parent)?v:null).filter(v=>v!==null) : r.up.find(v=>_cond('up', v, parent));
            if(false === res) res = up;
            else{
                // 合并up和put 以 id 属性作为主键.
                if(up instanceof Array){
                    up.forEach(v=>{
                        let _v = res.find(_v=>_v.id == v.id);
                        if(_v) Object.assign(_v, v);
                        else res.push(v);
                    })
                }else if(up instanceof Object){
                    Object.assign(res, up);
                }
            }
        }

        if(res instanceof Array){
            if(res.length > 0){
                if(next){
                    res.forEach(v=>{
                        let ret = execSubCondition(next, data, v);
                        if(false !== ret){
                            have = true;
                            Object.assign(final, ret)
                        }
                    })
                }
                if(get){
                    have = true;
                    final[name] = res;
                }
            }
        }else if(res instanceof Object){
            if(next){
                let ret = execSubCondition(next, data, res);
                if(false !== ret){
                    have = true;
                    Object.assign(final, ret)
                }
            }
            if(get){
                have = true;
                final[name] = res;
            }
        }
    }
    return have ? final : false;
},
execCondition = (cond, data)=>{
    if(cond instanceof Array){
        let r = {}, have;
        for(let i=0; i<cond.length; i++){
            let o = execSubCondition(cond[i], data);
            if(false !== o){
                have = true;
                Object.assign(r, o);
            }
        }
        return have? r : false;
    }else if(cond instanceof Object){
        return execSubCondition(cond, data);
    }
    return false;
};

let sohooks = [];
function Socket()
{
    let reConnect = false, isStart = false, url = null, conn = null, connTmId = null, _seqNumber = 1, cblist = {}, cmdlist = [], evtList = {}, _auth = null;

    const me = this, state = {
        STATE_OFFLINE: 0,
        STATE_CONNECTING: 1,
        STATE_CONNECTED: 2,
        STATE_ONLINE: 3
    }

    let props = reactive({
        ready: false,
        state: 0,
    });

    const start = (auth)=>{
        _auth = auth ?? _auth;
        if(conn && conn.readyState == WebSocket.OPEN) return

        isStart = true
        props.state = state.STATE_CONNECTING;

        let s = new WebSocket(url);

        s.onopen = (e)=>{
            //console.log('[onopen], readyState = ', e.target.readyState, e.target);
            props.state = state.STATE_CONNECTED;
            console.log('onopen', _auth)
        }

        s.onmessage = (e)=>{
            let c = e.target, msg = JSON.parse(e.data);
            if(msg.hasOwnProperty('_SEQ')){
                if(0 == msg._SEQ){
                    if(msg.success == true && msg.done == true){
                        conn = c;
                        clearTimeout(connTmId)
                        connTmId = null;
                        let cl = cmdlist;
                        cmdlist = [];
                        cl.forEach(cmd=>{
                            c.send(cmd)
                        })
                        props.state = state.STATE_ONLINE;
                        props.ready = true;
                        sohooks.forEach(hook=>hook.resolve(me))
                        sohooks = [];
                    }
                }
                // 服务器响应消息
                let re = cblist[msg._SEQ], done = msg.hasOwnProperty('done') ? msg.done : true;
                if(re){
                    if(done){
                        const _SEQ = msg._SEQ;
                        clearTimeout(re.tmoId);
                        delete cblist[_SEQ];
                        msg.success ? re.resolve(msg) : re.reject(msg)
                    } else {
                        re.update && re.update(msg)
                    }
                }
            }else if(msg.hasOwnProperty('event')){
                //服务器推送事件
                const ev = msg.event;
                for(const rkey in evtList){
                    const m = evtList[rkey];
                    switch(m.constructor.name){
                    case 'Object':
                        m.hasOwnProperty(ev) && m[ev](msg);
                        break;
                    case 'Function':
                        m(msg);
                        break;
                    }
                }
            }
        };

        s.onclose = (e)=>{
            let c = e.target;
            props.ready = false;

            // 处理所有未完成的事件.
            for(let k in cblist){
                clearTimeout(cblist[k]['tmoId']);
                cblist[k].reject({success: false, message: '连接异常断开.', done: true})
            }
            cblist = {}

            c.onopen = c.onmessage = c.onclose = c.onerror = null;
            if(reConnect && isStart && null === connTmId){
                props.state = state.STATE_CONNECTING;
                connTmId = setTimeout(()=>{
                    connTmId=null;
                    start()
                }, 2000);
            }else{
                props.state = state.STATE_OFFLINE;
            }
            console.log('[onclose], readyState = ', c.readyState, e);
        }
        s.onerror = (e)=>{
            console.log('[onerror], ', e.target);
        }

        connTmId = setTimeout(()=>{
            connTmId=null;
            s.onopen = s.onmessage = s.onclose = s.onerror = null;
            s.close();
            start();
        }, 8000);
    },
    stop = ()=>{
        isStart = false
        conn?.close()
        props.state = state.STATE_OFFLINE;
    }

    const methods = {
        _config:(c)=>{
            if(true === c.reConnect) reConnect = true;
        },
        start: (auth)=>{
            //url = `wss://s-${auth.house.server_id}-${auth.house.node_id}.exti.cc/index/channel?token=${auth.token}`;
            url = baseAddr + `?token=${auth.token}&sid=${auth.sid}`;
            console.log('[socket] start', url)
            start(auth)
        },
        stop: ()=>{
            console.log('[socket] stop')
            stop()
        },
        rpc: (action, payload, timeout, force)=>{
            let _SEQ = _seqNumber ++;
            //console.log(_SEQ, props.ready)
            if(isNaN(parseInt(timeout))) timeout = 8000;
            timeout = Math.max(timeout, 3000);

            return new PromiseEx((resolve, reject, update, cancel)=>{
                if(props.ready || force){
                    const pkt = JSON.stringify({action, data:payload, _SEQ, timeout}),
                    tmoId = setTimeout((s)=>{
                        let re = cblist[s];
                        if(re){
                            delete cblist[s];
                            cmdlist.hasOwnProperty(s)  && delete cmdlist[s];
                            delete cmdlist[s];
                            re.reject({success: false, message: '服务器响应超时.', done: true})
                        }
                    }, timeout, _SEQ);
                    cblist[_SEQ] = {_SEQ, resolve, reject, update, tmoId}
                    if(props.ready){
                        try{ conn.send(pkt); }catch(e){ cmdlist[_SEQ] = pkt;}
                    }else{
                        cmdlist[_SEQ] = pkt;
                    }
                }else{
                    reject({success: false, message: '网络离线, 请稍后再试.', done: true})
                }
            })
        },
        bind: (rkey, callbacks)=>evtList[rkey] = callbacks,
        unbind:(rkey)=>delete evtList[rkey],
        watchSync:(rkey, cond, cb)=>{
            const _cb = (msg)=>{
                let o = execCondition(cond, msg.data);
                o && cb(o);
            }
            me.bind(rkey, {sync: _cb});
        }
    }
    Object.assign(this, toRefs(props), methods, state);
}



let socket = new Socket();


const install = (app, opt)=>{
    socket._config(opt);
    app.provide(hskey, socket)
    Session.logined.value  && socket.start(Session.auth.value);
    watch(Session.logined, (logined)=>{
        logined ? socket.start(Session.auth.value) : socket.stop()
    });
}, useSocket = ()=>{
    return new Promise((resolve, reject)=>{
        if(socket.state.value == socket.STATE_ONLINE){
            resolve(socket);
        }else{
            sohooks.push({resolve, reject})
        }
    })
}, imgUrl = `http://${baseAddr}/img/photo/`,index = {
    version: '1.0.0',
    useSocket,
    install,
    imgUrl
}


export default index
export {useSocket, socket, install, imgUrl}